package rlm.rlcontainer;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.Border;

import linearProgramming.LinearProgrammingException;
import rlm.TabBounds;
import rlm.constraints.Directions;
import alm.Area;
import alm.Column;
import alm.LayoutSpec;
import alm.Row;
import alm.XTab;
import alm.YTab;

public class PlaceHolder {

	protected static int count = 0;

	private Dimension preferredSize, maximumSize, minimumSize;
	protected final String name;
	
//	protected JComponent component;
	protected ComponentHandler componentHandler;
	private PlaceHolder innerPlaceHolder;

	protected Map<Directions, List<PlaceHolder>> borders;

	private OrganizationTechnique organization;
	
	// ------- spatial parameters ----
	protected List<PlaceHolder> right, left, top, bottom;
	private TabBounds innerBounds;
	private TabBounds outerBounds;
	private Point location;
	
	private Row row;
	private Column column;

	// ------- visual parameters ----
	private Border border;
	
	/**
	 * primary constructor
	 * 
	 * @param name
	 */
	public PlaceHolder(String name) {
		this.name = name;
		right = new ArrayList<PlaceHolder>();
		top = new ArrayList<PlaceHolder>();
		left = new ArrayList<PlaceHolder>();
		bottom = new ArrayList<PlaceHolder>();

		innerBounds = new TabBounds();
		outerBounds = new TabBounds();

		borders = new HashMap<Directions, List<PlaceHolder>>();
		borders.put(Directions.Bottom, bottom);
		borders.put(Directions.Top, top);
		borders.put(Directions.Right, right);
		borders.put(Directions.Left, left);
		
		componentHandler = new NullComponentHandler();
		minimumSize = new Dimension(0,0);
		
		organization = new DefaultOrganizationTechnique();
	}

	public PlaceHolder(JComponent comp) {
		this(comp.getName());
		this.setComponent(comp);
	}

	public List<PlaceHolder> getPlaces(Directions dir) {
		return borders.get(dir);
	}

	public List<PlaceHolder> getRight() {
		return right;
	}

	public PlaceHolder addRight(PlaceHolder p) {
		// System.out.println(getName() + " addRight " + p.getName());
		getRight().add(p);
		// p.addLeft(this);
		return p;
	}

	public List<PlaceHolder> getLeft() {
		return left;
	}

	public PlaceHolder addLeft(PlaceHolder p) {
		// System.out.println(getName() + " addLeft " + p.getName());
		getLeft().add(p);
		return p;
	}

	public List<PlaceHolder> getTop() {
		return top;
	}

	public PlaceHolder addTop(PlaceHolder p) {
		// System.out.println(getName() + " addTop " + p.getName());
		getTop().add(p);
		return p;
	}

	public List<PlaceHolder> getBottom() {
		return bottom;
	}

	public PlaceHolder addBottom(PlaceHolder p) {
		// System.out.println(getName() + " addBottom " + p.getName());
		getBottom().add(p);
		// p.addTop(this);
		return p;
	}

	/** 
	 * Need to refine the use of this function, with regarding to inner components
	 * 
	 * It is now marked as deprecated, to remind all external users that it should not be used.
	 * public for testing
	 * @return
	 */
	@Deprecated
	public JComponent getComponent() {
		if (innerPlaceHolder != null)
			return innerPlaceHolder.getComponent();
		return componentHandler.getComponent();
	}

	public void setComponent(JComponent component) {
		if (component == null)
			this.componentHandler = new NullComponentHandler();
		else
			this.componentHandler = new FullComponentHandler(component);
		
		// no need to steal all this data from the component, should be defined directly
//		setPreferredSize(component.getPreferredSize());
//		setMaximumSize(component.getMaximumSize());
//		setMinimumSize(component.getMinimumSize());
	}

	/**
	 * adds inner components to this component
	 * later do it with inner place holder and place holder policy
	 * @param inner
	 */
	public void addInnerComponent(JComponent inner){
		componentHandler.add(inner);
	}
	
	/**
	 * Removes all inner components.
	 * Do it later with inner place holder
	 */
	public void clearInner(){
		componentHandler.removeAll();
	}
	
	public Dimension getMaximumSize() {
		return maximumSize;
	}

	public void setMaximumSize(Dimension maximumSize) {
		this.maximumSize = maximumSize;
		if (this.area != null)
			try {
				this.area.setMaxContentSize(maximumSize);
			} catch (LinearProgrammingException e) {
				e.printStackTrace();
			}
	}

	public void setMaximumSize(int width, int height) {
		setMaximumSize(new Dimension(width, height));
	}

	public Dimension getMinimumSize() {
		return minimumSize;
	}

	public void setMinimumSize(Dimension minimumSize) {
		this.minimumSize = minimumSize;
		if (this.area != null)
			this.area.setMinContentSize(minimumSize);
	}

	public void setMinimumSize(int width, int height) {
		setMinimumSize(new Dimension(width, height));
	}

	public String getName() {
		return name;
	}

	public Row getRow() {
		return row;
	}

	public void setRow(Row row) {
		this.row = row;

		getOuterBounds().setTopTab(row.getTop());
		getOuterBounds().setBottomTab(row.getBottom());
	}

	public Column getColumn() {
		return column;
	}

	public void setColumn(Column column) {
		this.column = column;
		getOuterBounds().setRightTab(column.getRight());
		getOuterBounds().setLeftTab(column.getLeft());
	}

	public boolean isOuterComplete() {
		return getOuterBounds().isComplete();
	}

	public boolean hasAllTabs() {
		return isOuterComplete();
	}

	private boolean simple = true;

	private Area area;

	public boolean isSimple() {
		return simple;
	}

	public void setSimple(boolean s) {
		this.simple = s;
	}

	public int getXCount() {
		return 1;
	}

	public int getYCount() {
		return 1;
	}

	public String toString() {
		String ret = getName();
		String sides = "[" + getLeft().size() + "," + getTop().size() + "," + getRight().size()
				+ "," + getBottom().size() + "]";

		ret += "\t" + sides + "::" + getOuterBounds().toString();

		return ret;
	}

	public void setPreferredSize(Dimension preferredSize) {
		this.preferredSize = preferredSize;
		this.componentHandler.setPreferredSize(preferredSize);
		
		if (this.area != null) {
			try {
				this.area.setPreferredContentSize(preferredSize);
			} catch (LinearProgrammingException e) {
				e.printStackTrace();
			}
		}
	}

	public void setSize(int width, int height) {
		this.setPreferredSize(width, height);
		this.setMaximumSize(new Dimension(width, height));
		this.setMinimumSize(width, height);
	}

	public void setPreferredSize(int width, int height) {
		setPreferredSize(new Dimension(width, height));
	}

	public Dimension getPreferredSize() {
		return preferredSize;
	}

	// TODO: when preferred size is in %
	public Dimension getPreferredSize(Container p) {
		return new Dimension(calcPer(p.getWidth(), getPreferredSize().width), calcPer(
				p.getHeight(), getPreferredSize().height));
	}

	private int calcPer(int orig, int per) {
		double ans = orig;
		double m = (per * 1.0) / 100;

		ans = (ans * m);
		return (int) ans;
	}

	public int getHeight() {
		return this.getPreferredSize().height;
	}

	public int getWidth() {
		return this.getPreferredSize().width;
	}

	public void setHeight(int height) {
		int width = getWidth();
		setPreferredSize(width, height);
	}

	public void setWidth(int width) {
		int height = getHeight();
		setPreferredSize(width, height);
	}

	public Color getBackground() {
		return componentHandler.getBackground();
	}

	public void setBackground(Color bg) {
		componentHandler.setBackground(bg);
	}

	public void setLocation(int x, int y) {
		//componentHandler.setLocation(x, y);
		location = new Point(x,y);
	}

	public Point getLocation() {
		//return componentHandler.getLocation();
		return this.location;
	}

	public void createArea(LayoutSpec ls) {
		JComponent comp = createComponent();
		if (comp != null)
			createArea1(ls, comp);
	}
	
	
	private JComponent createComponent(){
		JComponent comp = getComponent();
	
		if (comp == null)
			return null;

		// TODO :: WHY???
//		if (ls.areaOf(component) != null)
//			return null;

		if (organization.isScrollable()) {
			// take all inner component, add them to the scroll pane
			JPanel scrollPanel = new JPanel();
			JScrollPane pane = new JScrollPane(scrollPanel,
					JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
					JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

			Component[] components = comp.getComponents();
			for (Component component : components) {
				scrollPanel.add(component);
			}
			comp.add(pane);
		}
		
//		if (getBorder() != null)
		comp.setBorder(getBorder());
			
		return comp;
	}
	

	/**
	 * Creates an area, and completes the tabs, in case some of them are null.
	 * This change to tabs is not permanent in this PlaceHolder
	 * 
	 * @param ls
	 * @return
	 */
	private void createArea1(LayoutSpec ls, JComponent comp) {
		area = ls.addArea((XTab) getOuterBounds().testAndSet(Directions.Left, ls),
				(YTab) getOuterBounds().testAndSet(Directions.Top, ls), (XTab) getOuterBounds()
						.testAndSet(Directions.Right, ls),
				(YTab) getOuterBounds().testAndSet(Directions.Bottom, ls), comp,
				getMinimumSize());

		try {
			// area.setPreferredContentSize(getPreferredSize());
			if (getMaximumSize() != null)
				area.setMaxContentSize(getMaximumSize());
			if (getMinimumSize() != null)
				area.setMinContentSize(getMinimumSize());
		} catch (Exception e) {
			System.out.println("createArea: could not set default size: " + e.getMessage());
		}
	}

	private void addInnerBounds(Area area) throws LinearProgrammingException {
		area.createLeftConstraint(getOuterBounds().getLeftTab());
		area.createTopConstraint(getOuterBounds().getTopTab());
		area.createRightConstraint(getOuterBounds().getRightTab());
		area.createBottomConstraint(getOuterBounds().getBottomTab());
	}

	public void clear() {
		clear(Directions.Left, Directions.Right);
		clear(Directions.Right, Directions.Left);
		clear(Directions.Bottom, Directions.Top);
		clear(Directions.Top, Directions.Bottom);

		try {
			if (this.area != null)
				area.remove();
		} catch (LinearProgrammingException e) {
			e.printStackTrace();
		}
	}

	private void clear(Directions current, Directions other) {
		for (PlaceHolder place : borders.get(current)) {
			place.borders.get(other).remove(this);
		}
		borders.get(current).clear();
	}

	public TabBounds getInnerBounds() {
		return innerBounds;
	}

	public TabBounds getOuterBounds() {
		return outerBounds;
	}

	public boolean contains(PlaceHolder a) {
		if (this == a)
			return true;
		else
			return false;
	}

	// /// Tabs functions, for easier rule access
	public YTab getTopTab() {
		return outerBounds.getTopTab();
	}

	public void setTopTab(YTab topTab) {
		// System.out.println("\tTop tab "+topTab +
		// " to "+getName()+", was: "+this.topTab);
		this.outerBounds.setTopTab(topTab);
	}

	public YTab getBottomTab() {
		return this.outerBounds.getBottomTab();
	}

	public void setBottomTab(YTab bottomTab) {
		// System.out.println("\tBottom tab "+bottomTab +
		// " to "+getName()+", was: "+this.bottomTab);
		this.outerBounds.setBottomTab(bottomTab);
	}

	public XTab getLeftTab() {
		return this.outerBounds.getLeftTab();
	}

	public void setLeftTab(XTab leftTab) {
		this.outerBounds.setLeftTab(leftTab);
	}

	public XTab getRightTab() {
		return this.outerBounds.getRightTab();
	}

	public void setRightTab(XTab rightTab) {
		this.outerBounds.setRightTab(rightTab);
	}

	public void setInnerPlaceHolder(PlaceHolder innerPlaceHolder) {
		this.innerPlaceHolder = innerPlaceHolder;
	}

	public PlaceHolder getInnerPlaceHolder() {
		return innerPlaceHolder;
	}

	public void setScrollable(boolean scroll) {
		this.organization.setScrollable(scroll);
	}

	public void setBorder(Border border) {
		this.border = border;
	}

	public Border getBorder() {
		return border;
	}


	public void setOrganization(OrganizationTechnique organize) {
		this.organization = organize;
	}

	public OrganizationTechnique getOrganization() {
		return organization;
	}
	
	public String OrganizationType(){
		return organization.getType();
	}


	interface ComponentHandler{
		public void add(JComponent c);
		public void setBackground(Color bg);
		public Color getBackground();
		public void setPreferredSize(Dimension preferredSize);
		public JComponent getComponent();
		public void removeAll();
		public boolean isNull();
		public boolean isNotNull();		
	}
	
	class FullComponentHandler extends NullComponentHandler implements ComponentHandler{
		final JComponent component;

		/**
		 * This component is not null!! if it is, the user is an idiot
		 * @param c
		 */
		public FullComponentHandler(JComponent c) {  component = c; }
		public void add(JComponent c){ component.add(c); }
		public void removeAll(){ component.removeAll(); }
		public boolean isNull(){ return false; }
		public boolean isNotNull(){  return true; }
		public JComponent getComponent(){ return component; }
		public void setPreferredSize(Dimension preferredSize) { component.setPreferredSize(preferredSize); }
		public void setBackground(Color bg) { component.setBackground(bg); }
		public Color getBackground() { return component.getBackground(); }
	}
	
	class NullComponentHandler implements ComponentHandler{
		public void add(JComponent c) {}
		public void removeAll() {}
		public boolean isNull() { return true; }
		public boolean isNotNull() { return false; }
		public JComponent getComponent() { return null; }
		public void setPreferredSize(Dimension preferredSize) {}
		public void setBackground(Color bg) {}
		public Color getBackground() { return null; }
		
	}
}
